﻿using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Threading;

namespace PpSmsSender
{
    public class PollingThread
    {
        #region State enum
        public enum State
        {
            Stopped,
            Running
        }
        #endregion

// ReSharper disable MemberCanBePrivate.Global
        public static volatile bool TraceFlag;
// ReSharper restore MemberCanBePrivate.Global

        private readonly ManualResetEvent _enterWaitEvent = new ManualResetEvent(true);
        private readonly string _name;
        private readonly Func<PollingThread, bool> _workdelegate;
        public Action Cleanup;
        public Thread WorkerThread;
        private TimeSpan _interval;
        private ManualResetEvent _pauseEvent = new ManualResetEvent(true);
        private ManualResetEvent _shutdownEvent = new ManualResetEvent(false);

        public PollingThread (Func<PollingThread, bool> workdelegate, string name)
        {
            this._name = name;
            this._workdelegate = workdelegate;
            this._interval = new TimeSpan(0, 0, 0, 0, 100);
        }

        public PollingThread (Func<PollingThread, bool> workdelegate, string name, TimeSpan interval)
            : this(workdelegate, name)
        {
            this._interval = interval;
        }

        public PollingThread (Func<PollingThread, bool> workdelegate, string name, long millis)
            : this(workdelegate, name, TimeSpan.FromMilliseconds(millis))
        {
        }

        public State ThreadState
        {
            get { return this.WorkerThread == null ? State.Stopped : State.Running; }
        }

        public bool IsCurrentThread
        {
            get { return this.WorkerThread == Thread.CurrentThread; }
        }

        public void Start ()
        {
            if (this.WorkerThread != null)
            {
                throw new InvalidAsynchronousStateException("Already running");
            }
            this.TryStart();
        }

        public void TryStart ()
        {
            if (this.WorkerThread == null)
            {
                this._pauseEvent = new ManualResetEvent(false);
                this._shutdownEvent = new ManualResetEvent(false);
                this.WorkerThread = new Thread(this.EventLoop);
                this.WorkerThread.Name = this._name;
                this.WorkerThread.Start();
            }
        }

        public void Stop ()
        {
            if (this.WorkerThread == null)
            {
                throw new InvalidAsynchronousStateException("Already stopped");
            }

            this.TryStop();

            this.WorkerThread = null;
        }

        public void TryStop ()
        {
            if (this.WorkerThread != null)
            {
                this.Resume(); //Run last time
                this._enterWaitEvent.WaitOne(10000);
                this._shutdownEvent.Set(); //Signal the shutdown event
                this._pauseEvent.Set(); //Make sure to resume any paused threads
                this.WorkerThread.Join(); //            }

                this.WorkerThread = null;
            }
        }

        public bool Resume ()
        {
            return this._pauseEvent.Set();
        }

        private void EventLoop ()
        {
            while (true)
            {
                long tStart = DateTime.Now.Ticks;
                //This kills our process
                if (this._shutdownEvent.WaitOne(0))
                {
                    break;
                }

                this._enterWaitEvent.Reset();
                this.Trace("Go thread " + this._name);
                bool requestStop = this._workdelegate(this);
                this.Trace("Pause thread " + this._name);

                this._enterWaitEvent.Set();

                long t2 = DateTime.Now.Ticks;

                TimeSpan duration = TimeSpan.FromTicks(t2 - tStart);

                if (this._interval > duration)
                {
                    _pauseEvent.Reset();
                    this._pauseEvent.WaitOne(this._interval.Subtract(duration));
                }
                Thread.Sleep(1);
                if (requestStop)
                {
                    break;
                }
            }
            this.Trace("End thread " + this._name);

            if (this.Cleanup != null)
            {
                this.Cleanup();
            }
        }

        private void Trace (string msg)
        {
            if (TraceFlag)
            {
                Debug.WriteLine(msg);
            }
        }
    }
}